package utils

import (
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/shopspring/decimal"
)

var _int64Type = reflect.TypeOf(int64(0))
var _durationType = reflect.ValueOf(time.Duration(0)).Type()

func ToString(value any, trimSpace ...bool) (str string) {
	if value == nil {
		return ""
	}
	switch v := value.(type) {
	case string:
		str = v
	case reflect.Value:
		return ToString(v.Interface())
	case fmt.Stringer:
		str = v.String()
	default:
		str = fmt.Sprintf("%v", v)
	}
	if len(trimSpace) > 0 && trimSpace[0] {
		return strings.TrimSpace(str)
	}
	return str
}

func ToFloat(value any) (float64, error) {
	switch v := value.(type) {
	case int:
		return float64(v), nil
	case int8:
		return float64(v), nil
	case int16:
		return float64(v), nil
	case int32:
		return float64(v), nil
	case int64:
		return float64(v), nil
	case uint:
		return float64(v), nil
	case uint8:
		return float64(v), nil
	case uint16:
		return float64(v), nil
	case uint32:
		return float64(v), nil
	case uint64:
		return float64(v), nil
	case float32:
		return float64(v), nil
	case float64:
		return float64(v), nil
	case string:
		return strconv.ParseFloat(v, 64)
	case reflect.Value:
		return ToFloat(v.Interface())
	default:
		return 0, fmt.Errorf("unsupported type:%v", value)
	}
}

func ToInteger[T int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64](value any) (T, error) {
	round := func(val decimal.Decimal) T {
		return T(val.Round(0).IntPart())
	}

	switch v := value.(type) {
	case int:
		return T(v), nil
	case int8:
		return T(v), nil
	case int16:
		return T(v), nil
	case int32:
		return T(v), nil
	case int64:
		return T(v), nil
	case uint:
		return T(v), nil
	case uint8:
		return T(v), nil
	case uint16:
		return T(v), nil
	case uint32:
		return T(v), nil
	case uint64:
		return T(v), nil
	case float32:
		return round(decimal.NewFromFloat32(v)), nil
	case float64:
		return round(decimal.NewFromFloat(v)), nil
	case reflect.Value:
		return ToInteger[T](v.Interface())
	case string:
		str := strings.ReplaceAll(ToString(v, true), ",", "")
		val, err := decimal.NewFromString(str)
		if err != nil {
			return 0, err
		}
		return round(val), nil
	default:
		if rv := reflect.ValueOf(value); rv.IsValid() && rv.Type().ConvertibleTo(_int64Type) {
			if converted := rv.Convert(_int64Type); converted.IsValid() {
				return T(converted.Int()), nil
			}
		}
		return 0, fmt.Errorf("unsupported type:%v", value)
	}
}

func IsFloat(kind reflect.Kind) bool {
	switch kind {
	case reflect.Float32, reflect.Float64:
		return true
	default:
		return false
	}
}

func IsInteger(kind reflect.Kind) bool {
	switch kind {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return true
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		return true
	default:
		return false
	}
}

func IsBaseValue(kind reflect.Kind) bool {
	return IsInteger(kind) || IsFloat(kind) || kind == reflect.Bool || kind == reflect.String
}

func GetSimpleFields(data any) map[string]string {
	if data == nil {
		return nil
	}
	var val = reflect.ValueOf(data)
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	var typ = val.Type()
	var num = typ.NumField()
	var res = make(map[string]string, num)
	for i := 0; i < num; i++ {
		field := typ.Field(i)
		res[field.Name] = ToString(val.FieldByName(field.Name))
	}
	return res
}

func SetSimpleFields(dest any, src map[string]string) {
	elem := reflect.ValueOf(dest).Elem()
	idxmap := GetFieldIndexMap(elem.Type())
	if len(idxmap) == 0 {
		return
	}
	for k, v := range src {
		if len(v) == 0 {
			continue
		}
		if idx, ok := idxmap[k]; ok {
			SetFieldValue(elem.Field(idx), v)
		} else {
			k = CamelCase(k)
			if idx, ok = idxmap[k]; ok {
				SetFieldValue(elem.Field(idx), v)
			}
		}
	}
}

func SetFieldValue(dest reflect.Value, value any) error {
	if !dest.CanSet() {
		return errors.New("field cannot be set")
	}

	srcValue := reflect.ValueOf(value)
	srcType, destType := srcValue.Type(), dest.Type()
	if srcType == destType {
		dest.Set(srcValue)
		return nil
	}

	assign := func(newValue any) {
		srcValue = reflect.ValueOf(newValue)
		srcType = srcValue.Type()
		value = newValue
	}
	if str, ok := value.(string); ok && destType == _durationType {
		if tmp, err := time.ParseDuration(str); err != nil {
			return err
		} else {
			assign(tmp)
		}
	} else if IsInteger(destType.Kind()) {
		if tmp, err := ToInteger[int64](value); err != nil {
			return err
		} else {
			assign(tmp)
		}
	} else if IsFloat(destType.Kind()) {
		if tmp, err := ToFloat(value); err != nil {
			return err
		} else {
			assign(tmp)
		}
	}

	if !srcType.ConvertibleTo(destType) {
		return fmt.Errorf("cannot convert from %v to %v", srcType, destType)
	}

	destValue := srcValue.Convert(destType)
	if destInt, err := ToInteger[int64](destValue.Interface()); err != nil {
		return err
	} else if srcInt, err := ToInteger[int64](value); err != nil {
		return err
	} else if srcInt != destInt {
		return fmt.Errorf("convert value:%v from %v to %v not equal", value, srcType, destType)
	} else {
		dest.Set(destValue)
	}
	return nil
}

func SetObjectFieldValue(object reflect.Value, field string, value any) (bool, error) {
	dest := object.FieldByName(field)
	if !dest.IsValid() {
		if len(field) > 2 {
			return false, nil
		}

		field = strings.ToUpper(field)
		dest = object.FieldByName(field)
		if !dest.IsValid() {
			return false, nil
		}
	}

	if err := SetFieldValue(dest, value); err != nil {
		return false, err
	}
	return true, nil
}
